import {type PropsWithChildren, type RefObject, useRef} from "react";
import Draggable, {type DraggableEvent} from "react-draggable";
import '../assets/draggable.css'

/**
 * When drag action start
 * @param _ unuse that
 * @param el dragging object at now
 * @param __ the object which you will drag on
 */
function onStart(_: DraggableEvent, el: RefObject<HTMLDivElement>, __: () => RefObject<any>) {
    el.current?.classList.add('dragging-element')
}

/**
 * The notifier that notifies (the system/user) of the status of whether the element should be deleted.
 */
type Notifier = (whateverDelete: boolean) => void

function onStop(_: DraggableEvent, el: RefObject<HTMLDivElement>, getTrash: () => RefObject<any>, notifyDeleter: Notifier | undefined) {
    // remove a `dragging` class name dragging stoped
    // remove a `trash-hover` class name at dragging stoped
    el.current?.classList.remove('dragging-element')
    getTrash().current?.classList.remove('trash-hover')

    // if two element is intersected.
    // To notify notifier
    if (isIntersecting(el, getTrash())) {
        notifyDeleter?.(true)
    } else {
        notifyDeleter?.(false);
    }
}

function onDragging(_: DraggableEvent, el: RefObject<HTMLDivElement>, getTrash: () => RefObject<any>) {
    // when dragging
    // if two elements are intersected,
    // control class name of the element is being dragged and the `trash` object
    if (isIntersecting(el, getTrash())) {
        el.current?.classList.add('dragging-element')
        getTrash().current?.classList.add('trash-hover')
    } else {
        getTrash().current?.classList.remove('trash-hover')
    }
}

/**
 * Judge intersection between two elements
 * @param e1 element one
 * @param e2 element two
 */
function isIntersecting(
    e1: RefObject<HTMLElement>,
    e2: RefObject<HTMLElement>
): boolean {

    if (!e1.current || !e2.current) {
        console.warn("One or both elements do not exist");
        return false;
    }

    const rect1 = e1.current.getBoundingClientRect();
    const rect2 = e2.current.getBoundingClientRect();

    return (
        rect1.left <= rect2.right &&
        rect1.right >= rect2.left &&
        rect1.top <= rect2.bottom &&
        rect1.bottom >= rect2.top
    );
}

interface Necessity {
    /**
     * get the target ref, it's a trash ref
     */
    getTrashRef: () => RefObject<any>,
    /**
     * notify the parent component when dragging
     * @param isDragging
     */
    whenDraggingNotifier?: (isDragging: boolean) => void,
    /**
     * notify the parent component when all dragging stopped
     */
    allStopped?: (whateverDelete: boolean) => void,
}

export function ElementDraggable({
                                     children,
                                     getTrashRef,
                                     whenDraggingNotifier,
                                     allStopped
                                 }: PropsWithChildren & Necessity) {
    const elementRef = useRef<HTMLDivElement>(null);
    return <Draggable nodeRef={elementRef}
                      position={{x: 0, y: 0}}
                      onStart={(e) => onStart(e, elementRef as RefObject<HTMLDivElement>, getTrashRef)}
                      onDrag={e => {
                          whenDraggingNotifier?.(true);
                          onDragging(e, elementRef as RefObject<HTMLDivElement>, getTrashRef)
                      }}
                      onStop={(e) => {
                          whenDraggingNotifier?.(false)
                          onStop(e, elementRef as RefObject<HTMLDivElement>, getTrashRef, allStopped)
                      }}>
        <div ref={elementRef}>
            {children}
        </div>
    </Draggable>
}